#include <stdio.h>
#include <stdlib.h>
#include <elf.h>


#define INIT_STRING "<6>init: command '%s' action=%s status=%d (%s:%d)\n"

Elf_Ehdr *parse_binary(const char *path)
{
    FILE *fp;
    unsigned long length;
    Elf_Ehdr *hdr;

    fp = fopen(path, "rb");
    if (fp == NULL) {
        perror("fopen");
        return NULL;
    }

    fseek(fp, 0, SEEK_END);
    length = ftell(fp);
    hdr = (Elf_Ehdr*)malloc(length);
    if (hdr == NULL) {
        perror("malloc");
        goto out;
    }

    fseek(fp, 0, SEEK_SET);
    if (fread(hdr, 1, length, fp) < sizeof (*hdr)) {
        fprintf(stderr, "bad file head. \n");
        goto out;
    }

    if (memcmp(hdr->e_ident, ELFMAG, SELFMAG) ||
            ((hdr->e_type != ET_EXEC) && (hdr->e_type != ET_DYN))) {
        printf("type = %d \n", hdr->e_type);
        fprintf(stderr, "bad file format. \n");
        goto out;
    }

    return hdr;

out:
    free(hdr);
    return NULL;
}

unsigned long get_virtual_base(Elf_Ehdr *hdr)
{
    int i;
    Elf_Shdr *shdrs, *shdr;
    char *secstr;
    int find = 0;

    shdrs = (Elf_Shdr *)((char*)hdr + hdr->e_shoff);
    secstr = (char*)hdr + shdrs[hdr->e_shstrndx].sh_offset;

    for (i = 1; i < hdr->e_shnum; i++) {
        shdr = &shdrs[i];
        if ((!strcmp(secstr + shdr->sh_name, ".text"))
            && (shdr->sh_type == SHT_PROGBITS)) {
            find = 1;
            break;
        }
    }

    if (find) {
        return shdr->sh_addr - shdr->sh_offset;
    }
    return 0x8000;
}


unsigned long get_entry_offset(const char *path)
{
    Elf_Ehdr *hdr = parse_binary(path);

    if (hdr == NULL) {
        return 0;
    }

    return hdr->e_entry - get_virtual_base(hdr);
}

unsigned long find_bl_pattern()
{

}

unsigned long get_rodata_offset(Elf_Ehdr *hdr, unsigned long *length)
{
    int i;
    Elf_Shdr *shdrs, *shdr;
    char *secstr;

    shdrs = (Elf_Shdr *)((char*)hdr + hdr->e_shoff);
    secstr = (char*)hdr + shdrs[hdr->e_shstrndx].sh_offset;

    for (i = 1; i < hdr->e_shnum; i++) {
        shdr = &shdrs[i];
        if ((!strcmp(secstr + shdr->sh_name, ".rodata"))
            && (shdr->sh_type == SHT_PROGBITS)) {
            *length = shdr->sh_size;
            return shdr->sh_offset;
        }
    }
    return 0;
}

unsigned long find_rodata_string(const char *path, const char *pattern)
{
    Elf_Ehdr *hdr = parse_binary(path);
    unsigned long size;
    unsigned long data_off = get_rodata_offset(hdr, &size);
    unsigned char *match;
    unsigned char *p;
    unsigned long ret = 0;
    int nread;

    FILE *fp = fopen(path, "rb");
    if (fp == NULL) {
        fprintf(stderr, "open failed \n");
        return 0;
    }

    printf("rodata off = %lu \n", data_off);
    fseek(fp, data_off, SEEK_SET);
    match = malloc(size);
    nread = 0;
    unsigned long total = 0;
    while((nread = fread(match + nread, 1, size, fp)) > 0) {
        total += nread;
        if (nread >= size) {
            break;
        }
    }

    p = match;
    while (p < (match + size - strlen(pattern))) {
        if (memcmp(p, pattern, strlen(pattern)) == 0) {
            ret = data_off + p - match;
            break;
        }
        p++;
    }

    free(match);
    printf("offset is 0x%08x \n", ret);
    fclose(fp);
    return ret;
}

int main(int argc, char *argv[])
{
    unsigned long string_off;
    if (argc < 2) {
        printf("usage %s <execute binary> \n", argv[0]);
        exit(1);
    }

    printf("_start offset of %s is 0x%x \n", argv[1], get_entry_offset(argv[1]));
    string_off = find_rodata_string(argv[1], INIT_STRING);
    printf("string offset is %lu \n", string_off);
    return 0;
}
